using System.Collections.Concurrent;
using System.Text;

namespace TUnit.Core.SourceGenerator;

/// <summary>
/// Implementation of ICodeWriter for generating formatted C# source code.
/// Automatically handles indentation and newlines based on code structure.
/// </summary>
public class CodeWriter : ICodeWriter
{
    private readonly StringBuilder _builder = new();
    private readonly string _indentString;
    internal int _indentLevel; // Keep old name for compatibility
    private bool _isNewLine = true;

    private static readonly ConcurrentDictionary<(string, int), string> _indentCache = new();

    public CodeWriter(string indentString = "    ", bool includeHeader = true)
    {
        _indentString = indentString;

        for (var i = 0; i <= 10; i++)
        {
            var key = (_indentString, i);
            _indentCache.TryAdd(key, string.Concat(Enumerable.Repeat(_indentString, i)));
        }

        if (includeHeader)
        {
            _builder.AppendLine("// <auto-generated/>");
            _builder.AppendLine("#pragma warning disable");
            _builder.AppendLine();
            _isNewLine = true;
        }
        else
        {
            _isNewLine = true;
        }
    }

    /// <summary>
    /// Sets the initial indentation level. Useful for inline code generation.
    /// </summary>
    public ICodeWriter SetIndentLevel(int level)
    {
        _indentLevel = Math.Max(0, level);
        return this;
    }

    /// <summary>
    /// Gets the cached indentation string for the specified level, building it if necessary.
    /// </summary>
    private string GetIndentation(int level)
    {
        var key = (_indentString, level);
        return _indentCache.GetOrAdd(key, static k => string.Concat(Enumerable.Repeat(k.Item1, k.Item2)));
    }

    /// <summary>
    /// Appends text to the current line, applying indentation if at the start of a new line.
    /// </summary>
    public ICodeWriter Append(string text)
    {
        if (string.IsNullOrEmpty(text))
        {
            return this;
        }

        if (_isNewLine)
        {
            _builder.Append(GetIndentation(_indentLevel));
            _isNewLine = false;
        }
        _builder.Append(text);
        return this;
    }

    public int IndentLevel => _indentLevel;

    /// <summary>
    /// Appends text and then ensures a newline.
    /// </summary>
    public ICodeWriter AppendLine(string text = "")
    {
        Append(text);
        EnsureNewLine();
        return this;
    }

    public ICodeWriter AppendLines(IEnumerable<string> lines)
    {
        foreach (var line in lines)
        {
            AppendLine(line);
        }
        return this;
    }

    public ICodeWriter AppendLines(params string[] lines)
    {
        return AppendLines(lines.AsEnumerable());
    }

    /// <summary>
    /// Ensures that the next text appended will start on a new line.
    /// </summary>
    public ICodeWriter EnsureNewLine()
    {
        if (!_isNewLine)
        {
            _builder.AppendLine();
            _isNewLine = true;
        }
        return this;
    }

    /// <summary>
    /// Increases the indentation level.
    /// </summary>
    public ICodeWriter Indent()
    {
        _indentLevel++;
        EnsureNewLine();
        return this;
    }

    /// <summary>
    /// Decreases the indentation level.
    /// </summary>
    public ICodeWriter Unindent()
    {
        if (_indentLevel > 0)
        {
            _indentLevel--;
        }
        EnsureNewLine();
        return this;
    }

    /// <summary>
    /// Begins a code block with automatic formatting.
    /// </summary>
    public IDisposable BeginBlock(string leadingText = "")
    {
        if (!string.IsNullOrEmpty(leadingText))
        {
            Append(leadingText);
        }

        AppendLine();
        AppendLine("{");

        Indent();

        return new IndentScope(this);
    }

    public ICodeWriter AppendBlock(string header, Action<ICodeWriter> body)
    {
        using (BeginBlock(header))
        {
            body(this);
        }
        return this;
    }

    public IDisposable Scope()
    {
        _indentLevel++;
        return new BlockScope(this, null);
    }

    public ICodeWriter AppendLineIf(bool condition, string line)
    {
        if (condition)
        {
            AppendLine(line);
        }
        return this;
    }

    public ICodeWriter AppendComment(string comment)
    {
        return AppendLine($"// {comment}");
    }

    public ICodeWriter AppendRaw(string multilineText)
    {
        if (string.IsNullOrEmpty(multilineText))
        {
            return this;
        }

        var lines = multilineText.Split(["\r\n", "\r", "\n"], StringSplitOptions.None);

        // Skip leading empty lines
        var startIndex = 0;
        while (startIndex < lines.Length && string.IsNullOrWhiteSpace(lines[startIndex]))
        {
            startIndex++;
        }

        // Skip trailing empty lines
        var endIndex = lines.Length - 1;
        while (endIndex >= startIndex && string.IsNullOrWhiteSpace(lines[endIndex]))
        {
            endIndex--;
        }

        // Process remaining lines
        for (var i = startIndex; i <= endIndex; i++)
        {
            AppendLine(lines[i].TrimEnd());
        }

        return this;
    }

    public override string ToString()
    {
        return _builder.ToString();
    }

    /// <summary>
    /// Begins an object initializer block that ensures balanced braces.
    /// </summary>
    public IDisposable BeginObjectInitializer(string declaration, string terminator = ";")
    {
        AppendLine(declaration);
        AppendLine("{");
        _indentLevel++;
        return new ObjectInitializerScope(this, terminator);
    }

    /// <summary>
    /// Begins an array initializer block that ensures balanced braces.
    /// </summary>
    public IDisposable BeginArrayInitializer(string declaration, string terminator = "")
    {
        AppendLine(declaration);
        AppendLine("{");
        _indentLevel++;

        return new ArrayInitializerScope(this, terminator);
    }

    public void Dispose()
    {
        _builder.Clear();
    }

    /// <summary>
    /// Internal helper class to manage indentation scope using IDisposable.
    /// </summary>
    private class IndentScope : IDisposable
    {
        private readonly CodeWriter _writer;
        private bool _disposed;

        public IndentScope(CodeWriter writer)
        {
            _writer = writer ?? throw new ArgumentNullException(nameof(writer));
        }

        public void Dispose()
        {
            if (!_disposed)
            {
                _writer.Unindent();
                _writer.AppendLine("}");
                _disposed = true;
            }
        }
    }

    // Legacy BlockScope for backward compatibility
    private class BlockScope : IDisposable
    {
        private readonly CodeWriter _writer;
        private readonly string? _closer;

        public BlockScope(CodeWriter writer, string? closer)
        {
            _writer = writer;
            _closer = closer;
        }

        public void Dispose()
        {
            _writer._indentLevel = Math.Max(0, _writer._indentLevel - 1);
            if (!string.IsNullOrEmpty(_closer))
            {
                _writer.AppendLine(_closer!);
            }
        }
    }

    /// <summary>
    /// Internal helper class to manage object initializer scope.
    /// </summary>
    private class ObjectInitializerScope : IDisposable
    {
        private readonly CodeWriter _writer;
        private readonly string _terminator;
        private bool _disposed;

        public ObjectInitializerScope(CodeWriter writer, string terminator)
        {
            _writer = writer ?? throw new ArgumentNullException(nameof(writer));
            _terminator = terminator;
        }

        public void Dispose()
        {
            if (!_disposed)
            {
                _writer._indentLevel = Math.Max(0, _writer._indentLevel - 1);
                _writer.Append("}");
                if (!string.IsNullOrEmpty(_terminator))
                {
                    _writer.Append(_terminator);
                }
                _writer.AppendLine();
                _disposed = true;
            }
        }
    }

    /// <summary>
    /// Internal helper class to manage array initializer scope.
    /// </summary>
    private class ArrayInitializerScope : IDisposable
    {
        private readonly CodeWriter _writer;
        private readonly string _terminator;
        private bool _disposed;

        public ArrayInitializerScope(CodeWriter writer, string terminator)
        {
            _writer = writer ?? throw new ArgumentNullException(nameof(writer));
            _terminator = terminator;
        }

        public void Dispose()
        {
            if (!_disposed)
            {
                _writer._indentLevel = Math.Max(0, _writer._indentLevel - 1);
                _writer.Append("}");
                if (!string.IsNullOrEmpty(_terminator))
                {
                    _writer.Append(_terminator);
                }
                _writer.AppendLine();
                _disposed = true;
            }
        }
    }
}
